//
// GLSLProgramObject.cpp - Wrapper for GLSL program objects
//
// Author: Louis Bavoil
// Email: sdkfeedback@nvidia.com
//
// Copyright (c) NVIDIA Corporation. All rights reserved.
////////////////////////////////////////////////////////////////////////////////

#define NV_REPORT_COMPILE_ERRORS
//#define NV_REPORT_UNIFORM_ERRORS
#include "GLee.h"
#include <iostream>
#include <stdio.h>

#include "GLSLProgramObject.h"
//#include <nvShaderUtils.h>
//#include <nvSDKPath.h>
//
//
////////////////////////////////////////////////////////////
inline GLuint CompileGLSLShader( GLenum target, const char* shader)
{
   GLuint object;

   object = glCreateShader( target);

   if (!object)
      return object;

   glShaderSource( object, 1, &shader, NULL);

   glCompileShader(object);

   // check if shader compiled
   GLint compiled = 0;
   glGetShaderiv(object, GL_COMPILE_STATUS, &compiled);

   if (!compiled)
   {
#ifdef NV_REPORT_COMPILE_ERRORS
      char temp[256] = "";
      glGetShaderInfoLog( object, 256, NULL, temp);
      fprintf( stderr, "Compile failed:\n%s\n", temp);
#endif
      glDeleteShader( object);
      return 0;
   }

   return object;
}

//
//
////////////////////////////////////////////////////////////
inline GLuint CompileGLSLShaderFromFile( GLenum target, const char* filename)
{
   FILE *shaderFile;
   char *text;
   long size;

   //must read files as binary to prevent problems from newline translation
   shaderFile = fopen( filename, "rb");

   if ( shaderFile == NULL)
      return 0;

   fseek( shaderFile, 0, SEEK_END);

   size = ftell(shaderFile);

   fseek( shaderFile, 0, SEEK_SET);

   text = new char[size+1];

   fread( text, size, 1, shaderFile);

   fclose( shaderFile);

   text[size] = '\0';

   GLuint object = CompileGLSLShader( target, text);

   delete []text;

   return object;
}



GLSLProgramObject::GLSLProgramObject() :
	_progId(0)
{
}

GLSLProgramObject::~GLSLProgramObject()
{
	destroy();
}

void GLSLProgramObject::destroy()
{
	for (unsigned i = 0; i < _vertexShaders.size(); i++) {
		glDeleteShader(_vertexShaders[i]);
	}
	for (unsigned i = 0; i < _fragmentShaders.size(); i++) {
		glDeleteShader(_fragmentShaders[i]);
	}
	if (_progId != 0) {
		glDeleteProgram(_progId);
	}
}

void GLSLProgramObject::attachVertexShader(std::string filename)
{
	//std::cerr << filename << std::endl;
    std::string resolved_path;

	 GLuint shaderId = CompileGLSLShaderFromFile(GL_VERTEX_SHADER, filename.c_str());
	 if (shaderId == 0) {
	   std::cerr << "Error: Vertex shader failed to compile" << std::endl;
		exit(1);
	 }
	 _vertexShaders.push_back(shaderId);
}

void GLSLProgramObject::attachFragmentShader(std::string filename)
{
   GLuint shaderId = CompileGLSLShaderFromFile(GL_FRAGMENT_SHADER, filename.c_str());
   if (shaderId == 0) {
       std::cerr << "Error: Fragment shader failed to compile" << std::endl;
    exit(1);
   }
}

void GLSLProgramObject::link()
{
	_progId = glCreateProgram();

    for (unsigned i = 0; i < _vertexShaders.size(); i++) {
        glAttachShader(_progId, _vertexShaders[i]);
    }

    for (unsigned i = 0; i < _fragmentShaders.size(); i++) {
        glAttachShader(_progId, _fragmentShaders[i]);
    }

    glLinkProgram(_progId);

    GLint success = 0;
    glGetProgramiv(_progId, GL_LINK_STATUS, &success);

    if (!success) {
        char temp[1024];
        glGetProgramInfoLog(_progId, 1024, NULL, temp);
        printf("Failed to link program:\n%s\n", temp);
		exit(1);
    }
}

void GLSLProgramObject::bind()
{
	glUseProgram(_progId);
}

void GLSLProgramObject::unbind()
{
	glUseProgram(0);
}

void GLSLProgramObject::setUniform(std::string name, GLfloat* val, int count)
{
	GLint id = glGetUniformLocation(_progId, name.c_str());
	if (id == -1) {
#ifdef NV_REPORT_UNIFORM_ERRORS
		std::cerr << m_vName << std::endl << m_fName << ":" << std::endl;
		std::cerr << "Warning: Invalid uniform parameter " << name << std::endl;
#endif
		return;
	}
	switch (count) {
		case 1:
			glUniform1fv(id, 1, val);
			break;
		case 2:
			glUniform2fv(id, 1, val);
			break;
		case 3:
			glUniform3fv(id, 1, val);
			break;
		case 4:
			glUniform4fv(id, 1, val);
			break;
	}
}

void GLSLProgramObject::setTextureUnit(std::string texname, int texunit)
{
	GLint linked;
	glGetProgramiv(_progId, GL_LINK_STATUS, &linked);
	if (linked != GL_TRUE) {
		std::cerr << "Error: setTextureUnit needs program to be linked." << std::endl;
		exit(1);
	}
	GLint id = glGetUniformLocation(_progId, texname.c_str());
	if (id == -1) {
#ifdef NV_REPORT_UNIFORM_ERRORS
		std::cerr << "Warning: Invalid texture " << texname << std::endl;
#endif
		return;
	}
	glUniform1i(id, texunit);
}

void GLSLProgramObject::bindTexture(GLenum target, std::string texname, GLuint texid, int texunit)
{
	glActiveTexture(GL_TEXTURE0 + texunit);
	glBindTexture(target, texid);
	setTextureUnit(texname, texunit);
	glActiveTexture(GL_TEXTURE0);
}

void GLSLProgramObject::bindTexture2D( std::string texname, GLuint texid, int texunit )
{
   bindTexture(GL_TEXTURE_2D, texname, texid, texunit);
}

void GLSLProgramObject::bindTexture3D( std::string texname, GLuint texid, int texunit )
{
   bindTexture(GL_TEXTURE_3D, texname, texid, texunit);
}

void GLSLProgramObject::bindTextureRECT( std::string texname, GLuint texid, int texunit )
{
   bindTexture(GL_TEXTURE_RECTANGLE_ARB, texname, texid, texunit);
}
